#include <chrono>
#include <iostream>
#include <list>
#include <map>
#include <memory>
#include <sstream>
#include <stdexcept>
#include <thread>

#include <boost/assign.hpp>

#include "../Board.h"
#include "../IBlockingEndPoint.h"
#include "../IGameStateLoader.h"
#include "../ISolver.h"
#include "../MessageEncoding.h"
#include "../Problem.h"
#include "../WebClient.h"

using namespace snake;

namespace
{
  
  std::string DirectionToString(EDirection direction)
  {
    static std::map<EDirection, std::string> map = boost::assign::map_list_of
      (UP, "UP")(DOWN, "DOWN")(LEFT, "LEFT")(RIGHT, "RIGHT");
    return map.at(direction);
  }

  class EndPointCallback
    : public IBlockingEndPointCallback
  {
  public:
    explicit EndPointCallback(ISolver &solver, IGameStateLoader &game_state_loader)
      : solver_(solver)
      , game_state_loader_(game_state_loader)
    {
    }

    virtual void OnReceive(const std::vector<char> &message, IBlockingEndPoint &endpoint) override
    {
      try
      {
        Problem problem(game_state_loader_.LoadBoard(StringFromBytes(message)));
        std::string solution = DirectionToString(solver_.Solve(problem));
        endpoint.Send(StringToBytes(solution));
      }
      catch (const std::exception &e)
      {
        std::cerr << "ERROR: " << e.what() << std::endl;
      }
    }

  private:
    ISolver &solver_;
    IGameStateLoader &game_state_loader_;
  };
}

class WebClient::Impl
{
public:
  Impl(IBlockingEndPoint &endpoint, IGameStateLoader &game_state_loader, ISolver &solver)
    : endpoint_(endpoint)
    , solver_(solver)
    , game_state_loader_(game_state_loader)
  {
  }

  void Run()
  {
    endpoint_.Loop(::EndPointCallback(solver_, game_state_loader_));
  }

private:
  IBlockingEndPoint &endpoint_;
  ISolver &solver_;
  IGameStateLoader &game_state_loader_;
};

WebClient::WebClient(IBlockingEndPoint &endpoint, IGameStateLoader &game_state_loader, ISolver &solver)
  : pimpl_(new Impl(endpoint, game_state_loader, solver))
{
}

WebClient::~WebClient()
{
  delete pimpl_;
}

void WebClient::Run()
{
  pimpl_->Run();
}